package com.ndp.fb.scheduler.annotation;

import com.ndp.fb.config.ConfigCentre;
import com.ndp.fb.scheduler.job.DaemonJob;
import com.ndp.fb.util.StringUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;

/**
 * Created by hunk.qin on 2015/3/10.
 */
@Component
public class CronScheduledAnnoationBeanPostProcessor  implements BeanPostProcessor, Ordered,
        EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
        SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();
    private final Set<Class<?>> nonAnnotatedClasses =
            Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
    public Map<String,Object> objectmap=new HashMap<>();
    private Object scheduler;
    private StringValueResolver embeddedValueResolver;
    private BeanFactory beanFactory;
    private ApplicationContext applicationContext;



    @Autowired
    private  ScheduleLock scheduleLock;
    private boolean isinit=true;

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    /**
     * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
     * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
     * to be wrapped as a TaskScheduler.
     */
    public void setScheduler(Object scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    /**
     * Making a {@link BeanFactory} available is optional; if not set,
     * {@link org.springframework.scheduling.annotation.SchedulingConfigurer} beans won't get autodetected and
     * a {@link #setScheduler scheduler} has to be explicitly configured.
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * Setting an {@link ApplicationContext} is optional: If set, registered
     * tasks will be activated in the {@link ContextRefreshedEvent} phase;
     * if not set, it will happen at {@link #afterSingletonsInstantiated} time.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        if (this.beanFactory == null) {
            this.beanFactory = applicationContext;
        }
    }


    @Override
    public void afterSingletonsInstantiated() {
        if (this.applicationContext == null) {
            // Not running in an ApplicationContext -> register tasks early...
            finishRegistration();
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext() == this.applicationContext) {
            // Running in an ApplicationContext -> register tasks this late...
            // giving other ContextRefreshedEvent listeners a chance to perform
            // their work at the same time (e.g. Spring Batch's job registration).
            finishRegistration();
        }
    }

    private void finishRegistration() {
        if (this.scheduler != null) {
            this.registrar.setScheduler(this.scheduler);
        }

        if (this.beanFactory instanceof ListableBeanFactory) {
            Map<String, SchedulingConfigurer> configurers =
                    ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
            for (SchedulingConfigurer configurer : configurers.values()) {
                configurer.configureTasks(this.registrar);
            }
        }

        if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
            Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
            try {
                // Search for TaskScheduler bean...
//                this.registrar.setScheduler(this.beanFactory.getBean(TaskScheduler.class));
                this.registrar.setScheduler(this.beanFactory.getBean("initThreadPoolTaskScheduler"));
            }
            catch (NoUniqueBeanDefinitionException ex) {
                throw new IllegalStateException("More than one TaskScheduler exists within the context. " +
                        "Remove all but one of the beans; or implement the SchedulingConfigurer interface and call " +
                        "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex);
            }
            catch (NoSuchBeanDefinitionException ex) {
                logger.debug("Could not find default TaskScheduler bean", ex);
                // Search for ScheduledExecutorService bean next...
                try {
                    this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class));
                }
                catch (NoUniqueBeanDefinitionException ex2) {
                    throw new IllegalStateException("More than one ScheduledExecutorService exists within the context. " +
                            "Remove all but one of the beans; or implement the SchedulingConfigurer interface and call " +
                            "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex);
                }
                catch (NoSuchBeanDefinitionException ex2) {
                    logger.debug("Could not find default ScheduledExecutorService bean", ex);
                    // Giving up -> falling back to default scheduler within the registrar...
                }
            }
        }

        this.registrar.afterPropertiesSet();
    }


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) {
        return getObject(bean, beanName);
    }

    public Object getObject(final Object bean, String beanName) {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        if (!this.nonAnnotatedClasses.contains(targetClass)) {
            final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
            ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                    for (CronScheduled scheduled :
                            AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, CronScheduled.class)) {
                        processScheduled(scheduled, method, bean);
                        annotatedMethods.add(method);
                    }
                }
            });
            if (annotatedMethods.isEmpty()) {
                this.nonAnnotatedClasses.add(targetClass);
                if (logger.isDebugEnabled()) {
                    logger.debug("No @CronScheduled annotations found on bean class: " + bean.getClass());
                }
            }
            else {
                // Non-empty set of methods
                if (logger.isDebugEnabled()) {
                    logger.debug(annotatedMethods.size() + " @CronScheduled methods processed on bean '" + beanName +
                            "': " + annotatedMethods);
                }
            }
        }
        return bean;
    }


    protected void processScheduled(CronScheduled scheduled, Method method, Object bean) {
        try {
                Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with @CronScheduled");
                Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with @CronScheduled");
                if(isinit)
                    objectmap.put(bean.getClass().getName(),bean);
                if (AopUtils.isJdkDynamicProxy(bean)) {
                    try {
                        // Found a @CronScheduled method on the target class for this JDK proxy ->
                        // is it also present on the proxy itself?
                        method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                    }
                    catch (SecurityException ex) {
                        ReflectionUtils.handleReflectionException(ex);
                    }
                    catch (NoSuchMethodException ex) {
                        throw new IllegalStateException(String.format(
                                "@CronScheduled method '%s' found on bean target class '%s' but not " +
                                        "found in any interface(s) for a dynamic proxy. Either pull the " +
                                        "method up to a declared interface or switch to subclass (CGLIB) " +
                                        "proxies by setting proxy-target-class/proxyTargetClass to 'true'",
                                method.getName(), method.getDeclaringClass().getSimpleName()));
                    }
                }
                else if (AopUtils.isCglibProxy(bean)) {
                    // Common problem: private methods end up in the proxy instance, not getting delegated.
                    if (Modifier.isPrivate(method.getModifiers())) {
                        throw new IllegalStateException(String.format(
                                "@CronScheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " +
                                        "be delegated to target bean. Switch its visibility to package or protected.",
                                method.getName(), method.getDeclaringClass().getSimpleName()));
                    }
                }

                Runnable runnable = new ScheduledMethodRunnable(bean, method);
                boolean processedSchedule = false;
                String errorMessage =
                        "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

                // Determine initial delay
                long initialDelay = scheduled.initialDelay();
                String initialDelayString = scheduled.initialDelayString();
                if (StringUtils.hasText(initialDelayString)) {
                    Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
                    if (this.embeddedValueResolver != null) {
                        initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
                    }
                    try {
                        initialDelay = Integer.parseInt(initialDelayString);
                    }
                    catch (NumberFormatException ex) {
                        throw new IllegalArgumentException(
                                "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
                    }
                }

                // Check cron expression
                String cron = ConfigCentre.getString(scheduled.cronkey());
                String enablestr=ConfigCentre.getString(scheduled.enablekey());
                Boolean enable;
                if(StringUtil.isEmpty(cron)){
                    cron=scheduled.cronkey();
                    logger.warn("!!!!!!!!!!!!!-----------类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，无法找见 " + scheduled.cronkey() + "的Cron配置，会使用Key为Cron表达式");
                }
                if(StringUtil.isEmpty(enablestr)){
                    enable= BooleanUtils.toBoolean(scheduled.enablekey());
                    logger.warn("!!!!!!!!!!!!!-----------类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，无法找见 " + scheduled.enablekey() + "的Enable的配置，使用：" + scheduled.enablekey());
                }
                else
                    enable= BooleanUtils.toBoolean(enablestr);
                if (StringUtils.hasText(cron)) {
                    Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
                    processedSchedule = true;
                    String zone = scheduled.zone();
                    if (this.embeddedValueResolver != null) {
                        cron = this.embeddedValueResolver.resolveStringValue(cron);
                        zone = this.embeddedValueResolver.resolveStringValue(zone);
                    }
                    TimeZone timeZone;
                    if (StringUtils.hasText(zone)) {
                        timeZone = StringUtils.parseTimeZoneString(zone);
                    }
                    else {
                        timeZone = TimeZone.getDefault();
                    }
                    if (scheduleLock.haslock) {
                        if (enable) {
                           try {
                               this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
                               logger.info("######类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，cron: " + cron);
                           }catch (Exception e){
                               logger.info("######类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册失败，cron: " + cron+"  errormsg:"+e.getMessage());
                           }
                        }else{
                            logger.info("######类：" + bean.getClass().getName() + ",方法：" + method.getName() + "调度状态为关闭，不注册调度，cron: " + cron);
                        }
                    } else {
                         if(bean.getClass().equals(DaemonJob.class)){
                             this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
                             logger.info("守护进程：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，cron: " + cron);
                         }else{
                             logger.info("没有获得lock锁，不注册任何调度任务，启动监听，持续获取共享锁。" );
                         }
                    }
                }

                // At this point we don't need to differentiate between initial delay set or not anymore
                if (initialDelay < 0) {
                    initialDelay = 0;
                }

                // Check whether we had any attribute set
                Assert.isTrue(processedSchedule, errorMessage);

        }
        catch (IllegalArgumentException ex) {
            throw new IllegalStateException("Encountered invalid @CronScheduled method '" + method.getName() + "': " + ex.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void destroy() {
       // this.scheduleLock.releaseLock();
        this.registrar.destroy();
    }


    public void reRegisterDestroy(){
        this.registrar.destroy();
        this.registrar.setCronTasksList(new ArrayList<CronTask>());
        this.isinit=false;
    }

    public  void reRegister(){
        this.registrar.afterPropertiesSet();
    }


}
